/**
 * // Copyright (c) 2023 KylinOS. All rights reserved.
 * //
 * // This program is free software; you can redistribute it
 * // and/or modify it under the terms of the GNU General Public
 * // License (version 2) as published by the FSF - Free Software
 * // Foundation*
 */

/**
 * // Copyright (C) 2023 ben LG <guoliben@kylinos.cn>
 */

package docextractor

import (
	"archive/zip"
	"bytes"
	"encoding/xml"
	"io"
	"os/exec"
	"strings"
	"time"

	"github.com/moipa-cn/pptx"

	"gitee.com/guoliben/kydlp-doctotext/extractor_interface"
	"gitee.com/guoliben/kydlp-doctotext/utils/ctrlledprint"
)

type DocPptX struct{}

func (extractor DocPptX) GetTextFromFile(path string, size int64, filetype string, depth int) ([]byte, string) {
	ctrlledprint.Println("提取文本: 正在解析pptx(file)文档...")

	var text bytes.Buffer

	start := time.Now()

	p, err := pptx.ReadPowerPoint(path)
	if err != nil {
		ctrlledprint.Println("Error opening pptx file:", err)
		return nil, ""
	}
	for _, slide := range p.Slides {
		decoder := xml.NewDecoder(strings.NewReader(slide))
		for {
			token, err := decoder.Token()
			if err != nil {
				if err == io.EOF {
					ctrlledprint.Println("End decoding pptx file:", err)
				} else {
					ctrlledprint.Println("Error decoding pptx file:", err)
				}
				break
			}
			switch element := token.(type) {
			case xml.CharData:
				text.Write(element)
			}
		}
	}

	elapsed := time.Since(start)

	return text.Bytes(), elapsed.String()
}

func (extractor DocPptX) GetTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) ([]byte, string) {
	ctrlledprint.Println("提取文本: 正在解析pptx(buffer)文档...")

	var text bytes.Buffer

	start := time.Now()

	var p pptx.PowerPoint
	p.Slides = make(map[string]string)
	p.NotesSlides = make(map[string]string)
	p.Themes = make(map[string]string)
	p.Images = make(map[string]string)
	f, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
	if err != nil {
		ctrlledprint.Println("Error creating pptx reader:", err)
		return nil, ""
	}
	p.Files = f.File
	for _, file := range p.Files {
		if strings.Contains(file.Name, "ppt/slides/slide") {
			slideOpen, _ := file.Open()
			p.Slides[file.Name] = string(readCloserToByte(slideOpen))
		}
		if strings.Contains(file.Name, "ppt/notesSlides/notesSlide") {
			notesSlideOpen, _ := file.Open()
			p.NotesSlides[file.Name] = string(readCloserToByte(notesSlideOpen))
		}
		if strings.Contains(file.Name, "ppt/theme/theme") {
			themeOpen, _ := file.Open()
			p.Themes[file.Name] = string(readCloserToByte(themeOpen))
		}
		if strings.Contains(file.Name, "ppt/media/image") {
			imageOpen, _ := file.Open()
			p.Images[file.Name] = string(readCloserToByte(imageOpen))
		}
		if strings.Contains(file.Name, "ppt/presentation.xml") {
			presentationOpen, _ := file.Open()
			p.Presentation = string(readCloserToByte(presentationOpen))
		}
	}
	for _, v := range p.GetSlidesContent() {
		text.Write(getContentByXml(v))
	}

	elapsed := time.Since(start)

	return text.Bytes(), elapsed.String()
}

func (extractor DocPptX) GetStringTextFromFile(path string, size int64, filetype string, depth int) (string, string) {
	ctrlledprint.Println("提取文本: 正在解析pptx(file)文档...")

	var text bytes.Buffer

	start := time.Now()

	p, err := pptx.ReadPowerPoint(path)
	if err != nil {
		ctrlledprint.Println("End opening pptx file:", err)
		return "", ""
	}
	for _, slide := range p.Slides {
		decoder := xml.NewDecoder(strings.NewReader(slide))
		for {
			token, err := decoder.Token()
			if err != nil {
				if err == io.EOF {
					ctrlledprint.Println("End decoding pptx file:", err)
				} else {
					ctrlledprint.Println("Error decoding pptx file:", err)
				}
				break
			}
			switch element := token.(type) {
			case xml.CharData:
				text.Write(element)
			}
		}
	}

	elapsed := time.Since(start)

	return text.String(), elapsed.String()
}

func (extractor DocPptX) GetStringTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) (string, string) {
	ctrlledprint.Println("提取文本: 正在解析pptx(buffer)文档...")

	var text bytes.Buffer

	start := time.Now()

	var p pptx.PowerPoint
	p.Slides = make(map[string]string)
	p.NotesSlides = make(map[string]string)
	p.Themes = make(map[string]string)
	p.Images = make(map[string]string)
	f, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
	if err != nil {
		ctrlledprint.Println("Error creating pptx reader:", err)
		return "", ""
	}
	p.Files = f.File
	for _, file := range p.Files {
		if strings.Contains(file.Name, "ppt/slides/slide") {
			slideOpen, _ := file.Open()
			p.Slides[file.Name] = string(readCloserToByte(slideOpen))
		}
		if strings.Contains(file.Name, "ppt/notesSlides/notesSlide") {
			notesSlideOpen, _ := file.Open()
			p.NotesSlides[file.Name] = string(readCloserToByte(notesSlideOpen))
		}
		if strings.Contains(file.Name, "ppt/theme/theme") {
			themeOpen, _ := file.Open()
			p.Themes[file.Name] = string(readCloserToByte(themeOpen))
		}
		if strings.Contains(file.Name, "ppt/media/image") {
			imageOpen, _ := file.Open()
			p.Images[file.Name] = string(readCloserToByte(imageOpen))
		}
		if strings.Contains(file.Name, "ppt/presentation.xml") {
			presentationOpen, _ := file.Open()
			p.Presentation = string(readCloserToByte(presentationOpen))
		}
	}
	for _, v := range p.GetSlidesContent() {
		text.Write(getContentByXml(v))
	}

	elapsed := time.Since(start)

	return text.String(), elapsed.String()
}

// 获取内容
func readCloserToByte(stream io.Reader) []byte {
	buf := new(bytes.Buffer)
	buf.ReadFrom(stream)
	return buf.Bytes()
}

func getContentByXml(s string) []byte {
	var allText bytes.Buffer

	decoder := xml.NewDecoder(strings.NewReader(s))
	for {
		token, err := decoder.Token()
		if err != nil {
			if err == io.EOF {
				ctrlledprint.Println("End decoding pptx file:", err)
			} else {
				ctrlledprint.Println("Error decoding pptx file:", err)
			}
			break
		}
		switch element := token.(type) {
		//case xml.StartElement:
		//	// 处理开始标签 <tag>
		//	if element.Name.Local == "t" { // <a:t> 文本段
		//		var txt string
		//		decoder.DecodeElement(&txt, &element)
		//		allText.WriteString(txt)
		//	}
		case xml.CharData:
			// 处理文本内容
			allText.Write(element)
		//case xml.EndElement:
		//	// 处理结束标签 </tag>
		}
	}
	allText.WriteString("\n") // 幻灯片之间换行

	return allText.Bytes()
}

func pptxProcessFile(path string) ([]byte, error) {
	// 构建PPTX任务处理器命令及其参数
	cmd := exec.Command("/opt/kydlp-control-discovery/bin/kydlp-tasktransfer", "--socket", unixSocketPath, "--task", "pptxtask", "--file", path)

	// 缓冲区用于存储命令的输出
	var out bytes.Buffer
	var errOut bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &errOut

	// 执行命令并等待其完成
	if err := cmd.Run(); err != nil {
		ctrlledprint.Println("执行kydlp-tasktransfer命令处理PPTX(file)任务出错:", errOut.String())
		return nil, err
	} else {
		ctrlledprint.Println("执行kydlp-tasktransfer命令处理PPTX(file)任务成功，输出:", out.String())
		return out.Bytes(), nil
	}
}

func pptxProcessBuffer(path string) ([]byte, error) {
	// 构建PPTX任务处理器命令及其参数
	cmd := exec.Command("/opt/kydlp-control-discovery/bin/kydlp-tasktransfer", "--socket", unixSocketPath, "--task", "pptxtask", "--buffer")

	// 缓冲区用于存储命令的输出
	var out bytes.Buffer
	var errOut bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &errOut

	// 执行命令并等待其完成
	if err := cmd.Run(); err != nil {
		ctrlledprint.Println("执行kydlp-tasktransfer命令处理PPTX(buffer)任务出错:", errOut.String())
		return nil, err
	} else {
		ctrlledprint.Println("执行kydlp-tasktransfer命令处理PPTX(buffer)任务成功，输出:", out.String())
		return out.Bytes(), nil
	}
}

func pptxProcessFileReturnString(path string) (string, error) {
	// 构建PPTX任务处理器命令及其参数
	cmd := exec.Command("/opt/kydlp-control-discovery/bin/kydlp-tasktransfer", "--socket", unixSocketPath, "--task", "pptxtask", "--file", path)

	// 缓冲区用于存储命令的输出
	var out bytes.Buffer
	var errOut bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &errOut

	// 执行命令并等待其完成
	if err := cmd.Run(); err != nil {
		ctrlledprint.Println("执行kydlp-tasktransfer命令处理PPTX(file)任务出错:", errOut.String())
		return "", err
	} else {
		ctrlledprint.Println("执行kydlp-tasktransfer命令处理PPTX(file)任务成功，输出:", out.String())
		return out.String(), nil
	}
}

func pptxProcessBufferReturnString(path string) (string, error) {
	// 构建PPTX任务处理器命令及其参数
	cmd := exec.Command("/opt/kydlp-control-discovery/bin/kydlp-tasktransfer", "--socket", unixSocketPath, "--task", "pptxtask", "--buffer")

	// 缓冲区用于存储命令的输出
	var out bytes.Buffer
	var errOut bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &errOut

	// 执行命令并等待其完成
	if err := cmd.Run(); err != nil {
		ctrlledprint.Println("执行kydlp-tasktransfer命令处理PPTX(buffer)任务出错:", errOut.String())
		return "", err
	} else {
		ctrlledprint.Println("执行kydlp-tasktransfer命令处理PPTX(buffer)任务成功，输出:", out.String())
		return out.String(), nil
	}
}

func pptxGetTextFromFile(path string, size int64, filetype string) ([]byte, string) {
	ctrlledprint.Println("提取文本: 正在解析pptx(file)文档...")

	start := time.Now()

	text, _ := pptxProcessFile(path)

	elapsed := time.Since(start)

	return text, elapsed.String()
}

func pptxGetTextFromBuffer(path string, data []byte, size int64, filetype string) ([]byte, string) {
	ctrlledprint.Println("提取文本: 正在解析pptx(buffer)文档...")

	pptxtask := DocumentBufferTask{
		FileContent: data,
		FileSize: size,
		FileType: filetype,
	}
	err := PptXBufferTaskQueue.Enqueue(pptxtask)
	if err != nil {
		ctrlledprint.Println("PPTX文档任务入队失败:", err)
		return nil, ""
	} else {
		ctrlledprint.Println("PPTX文档任务入队成功!")
	}
	ctrlledprint.Println("---> PPTX文档任务队列容量:", PptXBufferTaskQueue.GetCap(), "已经入队的任务数:", PptXBufferTaskQueue.GetLen())

	start := time.Now()

	text, _ := pptxProcessBuffer(path)

	elapsed := time.Since(start)

	return text, elapsed.String()
}

func pptxGetStringTextFromFile(path string, size int64, filetype string) (string, string) {
	ctrlledprint.Println("提取文本: 正在解析pptx(file)文档...")

	start := time.Now()

	text, _ := pptxProcessFileReturnString(path)

	elapsed := time.Since(start)

	return text, elapsed.String()
}

func pptxGetStringTextFromBuffer(path string, data []byte, size int64, filetype string) (string, string) {
	ctrlledprint.Println("提取文本: 正在解析pptx(buffer)文档...")

	pptxtask := DocumentBufferTask{
		FileContent: data,
		FileSize: size,
		FileType: filetype,
	}
	err := PptXBufferTaskQueue.Enqueue(pptxtask)
	if err != nil {
		ctrlledprint.Println("PPTX文档任务入队失败:", err)
		return "", ""
	} else {
		ctrlledprint.Println("PPTX文档任务入队成功!")
	}
	ctrlledprint.Println("---> PPTX文档任务队列容量:", PptXBufferTaskQueue.GetCap(), "已经入队的任务数:", PptXBufferTaskQueue.GetLen())

	start := time.Now()

	text, _ := pptxProcessBufferReturnString(path)

	elapsed := time.Since(start)

	return text, elapsed.String()
}

func (extractor DocPptX) GetTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	var retcode int
	var text []byte
	var elapsed string

	if multiProcessMode {
		text, elapsed = pptxGetTextFromFile(path, size, filetype)
	} else {
		text, elapsed = extractor.GetTextFromFile(path, size, filetype, depth)
	}

	if callback != nil {
		retcode, _ = callback(path, size, filetype, text, int64(len(text)), context)
	}

	return retcode, text, elapsed
}

func (extractor DocPptX) GetTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	var retcode int
	var text []byte
	var elapsed string

	if multiProcessMode {
		text, elapsed = pptxGetTextFromBuffer(path, data, size, filetype)
	} else {
		text, elapsed = extractor.GetTextFromBuffer(path, data, size, filetype, depth)
	}

	if callback != nil {
		retcode, _ = callback(path, size, filetype, text, int64(len(text)), context)
	}

	return retcode, text, elapsed
}

func (extractor DocPptX) GetStringTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	var retcode int
	var text, elapsed string

	if multiProcessMode {
		text, elapsed = pptxGetStringTextFromFile(path, size, filetype)
	} else {
		text, elapsed = extractor.GetStringTextFromFile(path, size, filetype, depth)
	}

	if callback != nil {
		retcode, _ = callback(path, size, filetype, text, int64(len(text)), context)
	}

	return retcode, text, elapsed
}

func (extractor DocPptX) GetStringTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	var retcode int
	var text, elapsed string

	if multiProcessMode {
		text, elapsed = pptxGetStringTextFromBuffer(path, data, size, filetype)
	} else {
		text, elapsed = extractor.GetStringTextFromBuffer(path, data, size, filetype, depth)
	}

	if callback != nil {
		retcode, _ = callback(path, size, filetype, text, int64(len(text)), context)
	}

	return retcode, text, elapsed
}
